#include "tsocket.h"
#include <assert.h>
#ifdef WIN32
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#endif

#ifdef WIN32
#define close			closesocket
#define socklen_t		int
#else
#define INVALID_SOCKET	-1
#endif

namespace tlib
{
	bool TSocket::EnvInit()
	{
#ifdef WIN32
		WSADATA wsaData;
		if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
		{
			WSACleanup();
			return false;
		}
#endif
		return true;
	}

	void TSocket::EnvFree()
	{
#ifdef WIN32
		WSACleanup();
#endif
	}

	TSocket::TSocket() : buffer_(new TBuffer(g_bufferPool))
	{
		sockfd_ = -1;
	}

	TSocket::TSocket(int sockfd) : buffer_(new TBuffer(g_bufferPool))
	{
		sockfd_ = sockfd;
	}

	TSocket::~TSocket()
	{
		Close();
	}

	bool TSocket::Validate()
	{
		return -1 != sockfd_;
	}

	bool TSocket::Listen(char* host, int port)
	{
		sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
		struct sockaddr_in server_addr;
		server_addr.sin_family = AF_INET;
		server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
		server_addr.sin_port = htons(port);
		if (-1 == bind(sockfd_, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)))
		{
			return false;
		}
		if (-1 == listen(sockfd_, 1024))
		{
			return false;
		}
		return true;
	}

	TSocket* TSocket::Accept()
	{
		int sin_size = sizeof(struct sockaddr_in);
		struct sockaddr_in client_addr;
		return new TSocket(accept(sockfd_, (struct sockaddr*)&client_addr, (socklen_t*)&sin_size));
	}

	bool TSocket::Connect(char* host, int port)
	{
		struct sockaddr_in server_addr;
		server_addr.sin_family = AF_INET;
		server_addr.sin_addr.s_addr = inet_addr(host);
		server_addr.sin_port = htons(port);
		if (-1 == connect(sockfd_, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)))
		{
			return false;
		}
		return true;
	}

	int TSocket::Send(char* buff, int len)
	{
		int nwritten = send(sockfd_, buff, len, 0);
		assert(nwritten == len);
		return nwritten;
	}

	int TSocket::Recv(char* buff, int len)
	{
		int nread = recv(sockfd_, buff, len, 0);
		if (nread == 0) return -1;
		if (nread == -1)
		{
#ifdef WIN32
			int error = GetLastError();
			if (WSAEWOULDBLOCK == error) return 0;
#else
			if (EAGAIN == errno) return 0;
#endif
			return -1;
		}
		return nread;
	}

	void TSocket::Close()
	{
		close(sockfd_);
		sockfd_ = INVALID_SOCKET;
	}

	int TSocket::Send(int dstfd, char* buff, int len)
	{
		int nwritten = send(dstfd, buff, len, 0);
		assert(nwritten == len);
		return nwritten;
	}

	int TSocket::Recv(int srcfd, char* buff, int len)
	{
		int nread = recv(srcfd, buff, len, 0);
		if (nread == 0) return -1;
		if (nread == -1)
		{
#ifdef WIN32
			int error = GetLastError();
			if (WSAEWOULDBLOCK == error) return 0;
#else
			if (EAGAIN == errno) return 0;
#endif
			return -1;
		}
		return nread;
	}

	int TSocket::WriteBuffer(int len)
	{
		return buffer_->ReadFd(sockfd_, len);
	}

	bool TSocket::ReadBuffer(char* buff, int len)
	{
		return buffer_->Read(buff, len);
	}

	bool TSocket::SetNonBlock(bool on)
	{
#ifdef WIN32
		unsigned long ul = on ? 1 : 0;
		return 0 == ioctlsocket(sockfd_, FIONBIO, (unsigned long*)&ul);
#else
		int flags = fcntl(sockfd_, F_GETFL, 0);
		if (on) return 0 == fcntl(sockfd_, F_SETFL, flags | O_NONBLOCK);
		else return 0 == fcntl(sockfd_, F_SETFL, flags&(~O_NONBLOCK & 0xffffffff));
#endif
	}
	bool TSocket::SetTcpNoDelay(bool on)
	{
		int optval = on ? 1 : 0;
		return 0 == ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, (char*)&optval, sizeof(optval));
	}
	bool TSocket::SetReuseAddr(bool on)
	{
		int optval = on ? 1 : 0;
		return 0 == ::setsockopt(sockfd_, IPPROTO_TCP, SO_REUSEADDR, (char*)&optval, sizeof(optval));
	}
	bool TSocket::SetReusePort(bool on)
	{
#ifdef WIN32
#define SO_REUSEPORT SO_REUSEADDR
#endif
		int optval = on ? 1 : 0;
		return 0 == ::setsockopt(sockfd_, IPPROTO_TCP, SO_REUSEPORT, (char*)&optval, sizeof(optval));
	}
	bool TSocket::SetKeepAlive(bool on)
	{
		int optval = on ? 1 : 0;
		return 0 == ::setsockopt(sockfd_, IPPROTO_TCP, SO_KEEPALIVE, (char*)&optval, sizeof(optval));
	}
}